package Server;

/*
 * File:    ThreadServidor.java
 * Coms:    Hilo lanzado por el servidor para gestionar con el cliente la negociación
 * 		   del espacio publicitario
 */

/**
 * @author Daniel Cabrera Ebana - 686013
 * @author Alberto Sanchez Romero - 662385
 * @since 
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.util.UUID;

import Bank.Banco;

public class ThreadServidor implements Runnable {

	Socket clientSocket;
	// variables para de datos de oferta
	int valla = 0;
	int tiempo = 0;
	String precio = "";
	
	// Precio de las vallas*tiempo

	int precioTiempoValla1 = 10;
	int precioTiempoValla2 = 5;


	// Monitor con recursos compartidos
	GestorRecursos monitorRecursos;
	// Variables del banco
	Socket socketAlServidor = null;
	String SERVER_ADDRESS = "";
	int SERVER_PORT = 0;
	static int timeout = 60000;// 60 segundos; si el cliente no responde se
								// cierra la conexion

	/* Ruta donde se encuentran/almacenan las imagenes */
	private static String userPath = System.getProperties().getProperty(
			"user.dir");
	private static File directorio = new File(userPath + "/img");

	/**
	 * Constructor de la clase
	 * 
	 * @param clientSocket
	 * @param monitorRecursos
	 * @param ipBanco
	 * @param puertoBanco
	 * @see GestorRecursos.java
	 * @see Banco.java
	 */
	public ThreadServidor(Socket clientSocket, GestorRecursos monitorRecursos,
			String ipBanco, int puertoBanco) {
		this.clientSocket = clientSocket;
		this.monitorRecursos = monitorRecursos;
		this.SERVER_ADDRESS = ipBanco;
		this.SERVER_PORT = puertoBanco;
	}

	/**
	 * Genera un precio según la valla y el tiempo solicitado
	 * 
	 * @param valla
	 * @param tiempo
	 * @return precio generado
	 */
	private String generarPrecio(int valla, int tiempo) {

		if (valla == 1) {
			precio = "" + tiempo * precioTiempoValla1;
		} else {
			precio = "" + tiempo * precioTiempoValla2;
		}

		return precio;
	}

	/**
	 * Comprobar que los datos de entrada son correctos
	 * @param datos (valla,tiempo).
	 * @return true = datos correctos, false = datos incorrectos.
	 */
	private boolean datosCorrectos(String datos) {
		boolean correcto = true;
		try {
			valla = Integer.parseInt(datos.substring(0, datos.indexOf(",")));
			tiempo = Integer.parseInt(datos.substring(datos.indexOf(",") + 1));

			if ((valla != 1 || valla != 2) && (tiempo <= 0)) {
				correcto = false;
			}

		} catch (NumberFormatException e) {
			correcto = false;
			return correcto;
		}

		return correcto;
	}

	// al ser una comunicacion con un servidor, este metodo tiene que
	// comportarse como un cliente
	/**
	 * Comunicar con el Banco para gestionar el cobro a un cliente
	 * 
	 * @param VISA
	 * @param cobro
	 * @return respuestaBanco respuesta del banco a la solicitud
	 * @see Banco.java
	 */
	private String comunicarBanco(String VISA, String cobro) {

		boolean exito; // ¿conectado?
		String respuestaBanco = "";
		exito = conectarBanco(10); // 10 intentos

		if (!exito) {
			System.err.println("Don't know about host:" + SERVER_ADDRESS);
			respuestaBanco = "-2"; // Error en la conexion
			System.exit(1); // abortar si hay problemas
		}

		// Ya hay conexión
		// Inicialización de los flujos de datos del socket
		// para la comunicación con el banco

		PrintWriter canalSalidaAlBanco = null;
		BufferedReader canalEntradaDelBanco = null;
		try {
			canalSalidaAlBanco = new PrintWriter(
					socketAlServidor.getOutputStream(), true);
			canalEntradaDelBanco = new BufferedReader(new InputStreamReader(
					socketAlServidor.getInputStream()));
		} catch (IOException e) { // abortar si hay problemas
			System.err.println("I/O problem:" + SERVER_ADDRESS);
			System.exit(1);
		}

		String userInput = VISA + "," + cobro;

		try {
			if (userInput != null) {

				System.out.println("---Comprobando los datos de pago del cliente---");
				canalSalidaAlBanco.println(userInput);
				respuestaBanco = canalEntradaDelBanco.readLine();
				/*
				 * El banco puede contestar: 
				 * -1 : Cuenta incorrecta 
				 * 0 : Saldo insuficiente 
				 * 1 : Cuenta correcta y saldo suficiente (Solo en este caso hago algo)
				 */
				if (respuestaBanco.equals("1")) {
					// random entre 0.0 y 6.0, para que sean mas altas las
					// posibilidades de pagar
					double aleatorioPagar = Math.random() * 7;
					if (aleatorioPagar < 5) {
						canalSalidaAlBanco.println("pagar");
					} else {
						canalSalidaAlBanco.println("no pagar");
						respuestaBanco = "-3";// no se ha realizado el pago
					}

					// esperar la respuesta de que banco ha hecho el pago
					if (canalEntradaDelBanco.readLine().equals("ok")) {
						respuestaBanco = "2"; // el pago se realizo
												// satisfactoriamente
					}
				}
			}

			// Al cerrar cualquiera de los canales de
			// comunicación usados, el socket
			// se cierra. Como no nos importa perder información
			// cerramos el canal de entrada.
			canalEntradaDelBanco.close();

			// Cierre del Socket para comunicarse con el servidor.
			socketAlServidor.close();
		} catch (Exception e) {
			System.err.println(e);
		}
		return respuestaBanco;

	}

	/**
	 * Intenta conectar con el banco un numero de veces marcado por el parametro
	 * de entrada
	 * 
	 * @param maxIntentos
	 * @return true = se ha conectado, false = error
	 */
	private boolean conectarBanco(int maxIntentos) {
		// hasta maxIntentos intentos de conexion, para
		// darle tiempo al servidor a arrancar
		boolean exito = false; // �hay servidor?
		int van = 0;

		while ((van < maxIntentos) && !exito) {
			try {
				socketAlServidor = new Socket(SERVER_ADDRESS, SERVER_PORT);
				exito = true;
			} catch (Exception e) {
				van++;
				System.err.println("Failures:" + van);
				try { // esperar 1 seg
					Thread.sleep(1000);
				} catch (InterruptedException e2) {
					e2.printStackTrace();
				}
			}
		}
		return exito;
	}

	public void run() {

		PrintWriter salHaciaCliente = null;
		BufferedReader entDesdeCliente = null;
		String destinationFileName = "";

		try {
			salHaciaCliente = new PrintWriter(clientSocket.getOutputStream(),
					true);
			entDesdeCliente = new BufferedReader(new InputStreamReader(
					clientSocket.getInputStream()));
		} catch (IOException e) {
			System.err.println(e);
			System.exit(-1);
		}
		
		
		// respuesta para el cliente acerca del servicio
		String respuesta = "";
		// variable para almacenar datos pasados por el cliente
		String datos = "";
		
		int cont = 0;
		try {
			System.out.println("---Llegada de un nuevo cliente---");
			if(monitorRecursos.getApagar()){
				salHaciaCliente.println("Apagado");
				datos = "refuse";
			}else{
				salHaciaCliente.println("No Apagado");
				// leer datos del cliente
				datos = entDesdeCliente.readLine();
			}
			

			// timeout
			this.clientSocket.setSoTimeout(timeout);

			// condiciones= solo puede hacerlo 3 veces y saldra en caso de que
			// el cliente rechace la oferta
			// cont<=3 para que si la confirmacion se da en la ultima vuelta sea
			// capaz de capturarla
			while (!datos.equals("refuse") && cont <= 3
					&& (!salHaciaCliente.checkError())) {

				// Si el cliente acepta la oferta
				if (datos.equals("accept")) {
					// System.out.println("Dentro de if accept");
					// provoco que no se repita mas el bucle
					cont = 4;
					// le digo al cliente que he recibido todo correcto
					respuesta = "1";
					salHaciaCliente.println(respuesta);
					// recibir visa
					String visa = entDesdeCliente.readLine();
					System.out.println("visa: " + visa);
					/*
					 * comunicarBanco puede contestar: 
					 * -1 : cuenta incorrecta 
					 * 0 : saldo insuficiente 
					 * 2 : Pago realizado 
					 * -2 : Error en conexion 
					 * -3 : no se realizo el pago
					 */
					System.out.println("---Estableciendo comunicación con el banco---");
					String banco = comunicarBanco(visa, precio);

					// Si se realiza la transaccion(2)
					if (banco.equals("2")) {
						System.out.println("La transacción se ha realizado.");
						salHaciaCliente.println("2");

						System.out.println("---Esperando URL imagen 1---");
						// RECOGER URL DE LA IMAGEN
						String URL = entDesdeCliente.readLine();

						// comprobar que la URL existe y descargar la imagen

						// comprobamos que existe la url y que mantenemos
						// coenxion con el cliente
						try {
							while (!getStatus(URL)
									&& (!salHaciaCliente.checkError())) {
								// Manda al cliente codigo de que la url no es
								// correcta
								salHaciaCliente.println("-1");
								salHaciaCliente
										.println("ERROR: URL no encontrada");
								System.out.println("Esperando URL imagen 2");
								URL = entDesdeCliente.readLine();

							}

							// Si la url es valida
							salHaciaCliente.println("0");

							// genera una identificador unico para la imagen
							destinationFileName = generarIdentificador(URL);
							System.out.println("destintation file : "
									+ destinationFileName);
							System.out.println("URL : " + URL);
							// y la guardo
							saveImage(URL, destinationFileName);

							// concatenar URL mas tiempo
							String URLtiempo = destinationFileName + ","
									+ tiempo;
							// mandar url + tiempo a la cola
							System.out.println("---Añadiendo imagen en la lista de espera---");
							monitorRecursos.addcolaValla(valla, URLtiempo);

							// mandar mensaje al cliente con el tiempo
							// aproximado de
							// la visualizacion
							
							int tiempoEstimado = monitorRecursos
									.getTiempoEstimado(valla,
											destinationFileName);
							
							String estimacion = tiempoEstimado+"";
							salHaciaCliente.println(estimacion);
							// en caso de que salga mal

						} catch (Exception e) {
							System.out
									.println("Conexion perdida con el cliente durante URL");
							monitorRecursos.removeCliente();
						}

					} else {
						System.out
								.println("Error con el banco, avisando al cliente...");
						salHaciaCliente.println(banco);
					}
				} else {
					
					if (datosCorrectos(datos)) {
						System.out.println("datos correctos");
						respuesta = "1";
						if (respuesta.equals("1")) {
							if (monitorRecursos.getsize(valla) > 10) {
								System.out
										.println("No hay espacio en valla seleccionada");
								respuesta = "-1";
								salHaciaCliente.println(respuesta);
							} else {
								salHaciaCliente.println(respuesta);
								// generar y mandar al cliente el precio de su
								// peticion
								System.out
										.println("---Generando precio y enviando al cliente---");
								salHaciaCliente.println(generarPrecio(valla,
										tiempo));

								// Cliente avisa si acepta o rechaza o rechaza y
								// propone
								// cliente propone nueva oferta
								// datos = entDesdeCliente.readLine();
								datos = entDesdeCliente.readLine();
								System.out.println("datos: " + datos);
							}
						} else if (!datosCorrectos(datos)) {
							System.out.println("Datos incorrectos");
							respuesta = "0";
							salHaciaCliente.println(respuesta);
						}
					}
				}
				cont++;
			}
			// Cerrar conexion con cliente
			salHaciaCliente.close();
			monitorRecursos.removeCliente();
			System.out.println("Cliente termina sesion");

			// Filtrar los tipos de excecpciones

			// timeout sobre el socket
		} catch (java.net.SocketTimeoutException e) {
			System.err.println(e);
			System.out.println("Timeout del cliente");
			monitorRecursos.removeCliente();

		}
		// checkError() sobre printwrriter hacia el cliente

		catch (IOException e) {
			System.err.println(e);
			System.out.println("Conexion perdida con el cliente");
			monitorRecursos.removeCliente();
			System.exit(-1);

		}

	}

	/**
	 * Descarga la imagen desde la URL y la almacena en un directorio
	 * @param imageUrl
	 * @param destinationFileName
	 */
	private void saveImage(String imageUrl, String destinationFileName)
			throws IOException {
		URL url = new URL(imageUrl);
		InputStream is = url.openStream();
		OutputStream os = new FileOutputStream(directorio
				+ System.getProperty("file.separator") + destinationFileName);

		byte[] b = new byte[2048];
		int length;
		System.out.println("---Descargando imagen---");
		while ((length = is.read(b)) != -1) {
			os.write(b, 0, length);
		}

		is.close();
		os.close();
		System.out.println("---Imagen descargada---");
	}


	/**
	 * Comprueba que la URL este accesible
	 * 
	 * @param url de la imagen a comprobar
	 * @return true = si la imagen esta accesible; false = si no esta accesible
	 * 
	 */
	private boolean getStatus(String url) throws IOException {

		boolean status = false;

		try {
			URL siteURL = new URL(url);
			HttpURLConnection connection = (HttpURLConnection) siteURL
					.openConnection();
			connection.setRequestMethod("GET");
			connection.connect();
			int code = connection.getResponseCode();

			// comprobar que la url existe
			// comprobar que el tipo de dato es una imagen
			if ((code == 200)
					&& getMimeType(url).substring(0, 6).equals("image/")) {
				status = true;

			}
		} catch (Exception e) {
			status = false;
		}
		return status;
	}

	/**
	 * Comprueba que el tipo de dato pasado en la URL es una imagen
	 * @param fileUrl
	 * @return tipo de fichero que hay en la URL image/jpeg o /image/png
	 * 
	 */
	private String getMimeType(String fileUrl) throws java.io.IOException,
			MalformedURLException {
		String type = null;
		URL u = new URL(fileUrl);
		URLConnection uc = null;
		uc = u.openConnection();
		type = uc.getContentType();
		return type;
	}

	/**
	 * genera un identificador unico para la imagen
	 * @param url
	 * @return UUID.jpg identificador único de la imagen con la extensión .jpg
	 */

	private static String generarIdentificador(String url) {

		String nombre;
		nombre = url.substring(url.lastIndexOf("."));// saco la extension
		nombre = UUID.randomUUID().toString() + nombre; // genero un UUID
		System.out.println(nombre);
		return nombre;

	}

}
